package view;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import view.subscriberpanels.SelectionPane;
import view.subscriberpanels.VisSubscriber;
import view.visualizations.XCLineVis;
import view.visualizations.XCVis;

/**
 * Singleton manager of the entire view. This is a singleton + sort of event bus.
 * All the other panels subscribe to this so that when the main vis changes, they will
 * all know about it.
 */
public class ViewManager {

	private static ViewManager instance;
	
	/**
	 * The components that need to be updated when the vis changes.
	 */
	private Set<VisSubscriber> subscribers;
	
	/**
	 * The current vis.
	 * // TODO make this a map or something so that we can keep history and go back? 
	 */
	private XCVis vis;
	private LinkedList<XCVis> visList;
	
	private String filterQuery;
	private String selectionQuery;
	private HashMap<String, String> filterMap; // string key to actual query
	private boolean selectionLock;
	
	private ViewManager() {
		selectionLock = true;
		subscribers = new HashSet<VisSubscriber>();
		vis = null;
		visList = new LinkedList<XCVis>();
		filterQuery = "";
		filterMap = new HashMap<String, String>();
	}
	
	public static ViewManager getInstance() {
		if (instance == null) {
			instance = new ViewManager();
		}
		return instance;
	}
	
	/**
	 * Go back to the last vis and remove the current one.
	 */
	public static void goBack() {
		instance.visList.removeLast();
		setCurrentVis(instance.visList.getLast(), false);
	}
	
	public static XCVis getCurrentVis() {
		if (ViewManager.getInstance().vis == null) {
			ViewManager.setCurrentVis(new XCLineVis());
		}
		return instance.vis;
	}
	
	public static void updateVis() {
		instance.vis.setupVis();
	}
	
	public static void selectAllVisibleRunners() {
		for (VisSubscriber subscriber : instance.subscribers) {
			if (subscriber instanceof SelectionPane) {
				((SelectionPane)subscriber).selectAllRunners();
			}
		}
	}
	
	public static void selectRunners(final String runner) {
		if (instance.selectionLock) {
			instance.selectionLock = false;
			for (VisSubscriber subscriber : instance.subscribers) {
				if (subscriber instanceof SelectionPane) {
					((SelectionPane)subscriber).selectRunner(runner);
				}
			}
			instance.selectionLock = true;
		}
	}
	
	/**
	 * I think this method causes the call-hierarchy of all these classes to be circular.
	 * A well-designed system probably shouldn't have this, but project due in 3 days > design.
	 */
	public static void notifySubscribers() {
		// Tell everyone the vis has changed.
		for (VisSubscriber subscriber : instance.subscribers) {
			subscriber.visChanged(instance.vis);
		}
	}
	
	/**
	 * I think this method causes the call-hierarchy of all these classes to be circular.
	 * A well-designed system probably shouldn't have this, but project due in 3 days > design.
	 */
	public static void notifyTableChange() {
		// Tell everyone the vis has changed.
		for (VisSubscriber subscriber : instance.subscribers) {
			subscriber.tableChanged();
		}
	}
	
	public static void setCurrentVis(final XCVis vis) {
		setCurrentVis(vis, true);
	}
	
	private static void setCurrentVis(final XCVis vis, final boolean addToHistory) {
		if (!vis.getIsCanceled()) {
			if (addToHistory) {
				instance.visList.addLast(vis);
			}
			instance.vis = vis;
			
			// Tell everyone the vis has changed.
			for (VisSubscriber subscriber : instance.subscribers) {
				subscriber.visChanged(instance.vis);
			}
		}
	}
	
	public void addFilterQuery(final String key, final String query) {
		filterMap.put(key, query);
		updateFilterQuery();
	}
	
	public void setSelectionQuery(final String selectionQuery) {
		this.selectionQuery = selectionQuery;
	}
	
	public String getSelectionQuery() {
		return selectionQuery;
	}
	
	private void updateFilterQuery() {
		filterQuery = "";
		Set<String> keySet = filterMap.keySet();
		Set<String> cleanedKeySet = new HashSet<String>();
		for (String key : keySet) {
			if (!filterMap.get(key).equals("")) {
				cleanedKeySet.add(key);
			}
		}
		
		int i=0;
		for (String key : cleanedKeySet) {
			if (i < cleanedKeySet.size()-1) {
				filterQuery += "(" + filterMap.get(key) + ") AND ";
			} else {
				filterQuery += "(" + filterMap.get(key) + ")";
			}
			i++;
		}
		System.out.println("filter query: " + filterQuery);
		if (filterQuery.equals("")) {
			vis.removeAction("filter");
			vis.removeFilterAction();
		} else {
//			vis.addFilterAction(new VisibilityFilter("data",
//				(Predicate)(ExpressionParser.parse(filterQuery))));
			vis.addFilterAction(
					(Predicate)(ExpressionParser.parse(filterQuery)));
		}
	}
	
	public String getFilterQuery() {
		return filterQuery;
	}
	
	public static void subscribe(final VisSubscriber subscriber) {
		ViewManager.getInstance().subscribers.add(subscriber);
	}
	
}
